﻿#region The MIT License (MIT)
// The MIT License (MIT)
// Copyright (c) 2010 - 2013 Sovacode LLC
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions
// of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Windows;
using System.Xml;
using System.Xml.Serialization;
using Sovacode.RubiksCube.Common.Base;
using Sovacode.RubiksCube.Common.Managers.Settings.Data;
using Sovacode.RubiksCube.Common.Service;
using Sovacode.RubiksCube.Common.Text;
using Sovacode.RubiksCube.Common.ViewModel;

namespace Sovacode.RubiksCube.Common.Managers.Settings
{
    /// <summary>
    /// Base abstraction class for Settings Manager
    /// </summary>
    public abstract class SettingsManager : Manager<bool>, ISettingsManager
    {
        #region Put Setting

        #region Expression
        /// <summary>
        /// Put setting as <see cref="double"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutDoubleSetting(Expression<Func<double>> expression, double setting)
        {
            PutSetting(expression, setting);
        }
        /// <summary>
        /// Put setting as <see cref="int"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutInt32Setting(Expression<Func<int>> expression, int setting)
        {
            PutSetting(expression, setting);
        }
        /// <summary>
        /// Put setting as <see cref="string"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutStringSetting(Expression<Func<string>> expression, string setting)
        {
            PutSetting(expression, setting);
        }
        /// <summary>
        /// Put setting as <see cref="decimal"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutDecimalSetting(Expression<Func<decimal>> expression, decimal setting)
        {
            PutSetting(expression, setting);
        }
        /// <summary>
        /// Put setting as <see cref="char"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutCharSetting(Expression<Func<char>> expression, char setting)
        {
            PutSetting(expression, setting);
        }
        /// <summary>
        /// Put setting as <see cref="bool"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutBoolSetting(Expression<Func<bool>> expression, bool setting)
        {
            PutSetting(expression, setting);
        }

        #endregion

        #region Simple
        /// <summary>
        /// Put setting as <see cref="double"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutDoubleSetting(string settingName, double setting)
        {
            PutSetting(settingName, setting);
        }
        /// <summary>
        /// Put setting as <see cref="int"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutInt32Setting(string settingName, int setting)
        {
            PutSetting(settingName, setting);
        }
        /// <summary>
        /// Put setting as <see cref="string"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutStringSetting(string settingName, string setting)
        {
            PutSetting(settingName, setting);
        }
        /// <summary>
        /// Put setting as <see cref="decimal"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutDecimalSetting(string settingName, decimal setting)
        {
            PutSetting(settingName, setting);
        }
        /// <summary>
        /// Put setting as <see cref="char"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutCharSetting(string settingName, char setting)
        {
            PutSetting(settingName, setting);
        }
        /// <summary>
        /// Put setting as <see cref="bool"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutBoolSetting(string settingName, bool setting)
        {
            PutSetting(settingName, setting);
        }

        #endregion
        /// <summary>
        /// Put setting as specific type
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="expression">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public void PutSetting<T>(Expression<Func<T>> expression, T setting)
        {
            PutSetting(expression.ExtractName(), setting);
        }
        /// <summary>
        /// Put setting as specific type
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="settingName">Setting name in config file</param>
        /// <param name="setting">Value of setting</param>
        public abstract void PutSetting<T>(string settingName, T setting);

        #endregion

        #region Get Setting

        #region Expression
        /// <summary>
        /// Get setting from config file as <see cref="double"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public double GetDoubleSetting(Expression<Func<double>> expression)
        {
            return GetSetting(expression);
        }
        /// <summary>
        /// Get setting from config file as <see cref="int"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public int GetInt32Setting(Expression<Func<int>> expression)
        {
            return GetSetting(expression);
        }
        /// <summary>
        /// Get setting from config file as <see cref="string"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public string GetStringSetting(Expression<Func<string>> expression)
        {
            return GetSetting(expression);
        }
        /// <summary>
        /// Get setting from config file as <see cref="decimal"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public decimal GetDecimalSetting(Expression<Func<decimal>> expression)
        {
            return GetSetting(expression);
        }
        /// <summary>
        /// Get setting from config file as <see cref="char"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public char GetCharSetting(Expression<Func<char>> expression)
        {
            return GetSetting(expression);
        }
        /// <summary>
        /// Get setting from config file as <see cref="bool"/>
        /// </summary>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public bool GetBooleanSetting(Expression<Func<bool>> expression)
        {
            return GetSetting(expression);
        }

        #endregion

        #region Simple
        /// <summary>
        /// Get setting from config file as <see cref="double"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public double GetDoubleSetting(string settingName)
        {
            return GetSetting<double>(String.Intern(settingName));
        }
        /// <summary>
        /// Get setting from config file as <see cref="int"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public int GetInt32Setting(string settingName)
        {
            return GetSetting<int>(String.Intern(settingName));
        }
        /// <summary>
        /// Get setting from config file as <see cref="string"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public string GetStringSetting(string settingName)
        {
            return GetSetting<string>(String.Intern(settingName));
        }
        /// <summary>
        /// Get setting from config file as <see cref="decimal"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public decimal GetDecimalSetting(string settingName)
        {
            return GetSetting<decimal>(String.Intern(settingName));
        }
        /// <summary>
        /// Get setting from config file as <see cref="char"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public char GetCharSetting(string settingName)
        {
            return GetSetting<char>(String.Intern(settingName));
        }
        /// <summary>
        /// Get setting from config file as <see cref="bool"/>
        /// </summary>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public bool GetBooleanSetting(string settingName)
        {
            return GetSetting<bool>(String.Intern(settingName));
        }

        #endregion
        /// <summary>
        /// Get setting from config file as specific type
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="expression">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public T GetSetting<T>(Expression<Func<T>> expression)
        {
            return GetSetting<T>(expression.ExtractName());
        }
        /// <summary>
        /// Casting property to specific type
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="settingName">Setting name</param>
        /// <returns>Setting value</returns>
        protected virtual T CastTo<T>(string settingName)
        {
            return default(T);
        }
        /// <summary>
        /// Get setting from config file as specific type
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Value of setting</returns>
        public T GetSetting<T>(string settingName)
        {
            var someProperty = default(T);

            try
            {
                someProperty = CastTo<T>(String.Intern(settingName));

                Dictionary[String.Intern(settingName)] = true;

            } catch (Exception ex)
            {
                ShowError(string.Format(CategorizedBy.SettingsManager.CastErrorMessage, settingName, typeof (T).Name,
                                        ex.Message));

                Dictionary[String.Intern(settingName)] = false;
            }

            if (someProperty == null)
            {
                return default(T);
            }

            return someProperty;
        }
        
        #endregion

        #region List Saver/Loader
        /// <summary>
        /// Put <see cref="IList{T}"/> into config file
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="list"><see cref="IList{T}"/></param>
        /// <param name="settingName">Setting name in config file</param>
        public void SaveList<T>(IList<T> list, string settingName)
        {
            var property = GetStringSetting(String.Intern(settingName));

            if (property == null)
            {
                return;
            }

            using (var stream = new MemoryStream())
            {
                var serializer = new XmlSerializer(typeof(List<T>));

                serializer.Serialize(new XmlTextWriter(stream, Encoding.UTF8), list);

                PutSetting(String.Intern(settingName), Encoding.UTF8.GetString(stream.GetBuffer()));
            }
        }
        /// <summary>
        /// Get <see cref="IList{T}"/> from config file back
        /// </summary>
        /// <typeparam name="T">Type of setting</typeparam>
        /// <param name="settingName">Setting name in config file</param>
        /// <returns>Список</returns>
        public IList<T> RestoreList<T>(string settingName)
        {
            var list = new List<T>();

            var property = GetStringSetting(String.Intern(settingName));

            if (property == null)
            {
                return list;
            }

            if (string.IsNullOrWhiteSpace(property))
            {
                return list;
            }

            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(property)))
            {
                try
                {
                    var serializer = new XmlSerializer(typeof(List<T>));

                    list.AddRange((List<T>)serializer.Deserialize(new XmlTextReader(stream)));

                }
                catch (Exception ex)
                {
                    MessageBox.Show(
                        string.Format(CategorizedBy.SettingsManager.CastErrorMessage, settingName, typeof(T).Name, ex.Message),
                        CategorizedBy.WindowTitles.ErrorWindowTitle,
                        MessageBoxButton.OK,
                        MessageBoxImage.Error);
                }
                
            }

            return list;
        }

        #endregion

        #region Connection strings
        /// <summary>
        /// Put Data Access Settings in config. It could be a connection string or something else.
        /// </summary>
        /// <param name="connections"><see cref="IList{T}"/> of <see cref="DataAccessSetting"/></param>
        public void SaveConnectionString(IList<DataAccessSetting> connections)
        {
            SaveList(connections, CategorizedBy.SystemSettings.ConnectionStringsSetting);
        }
        /// <summary>
        /// Get Data Access Settings from config back.
        /// </summary>
        /// <returns><see cref="IList{T}"/> of <see cref="DataAccessSetting"/>></returns>
        public IList<DataAccessSetting> LoadConnectionStrings()
        {
            return RestoreList<DataAccessSetting>(CategorizedBy.SystemSettings.ConnectionStringsSetting);
        }

        #endregion
        /// <summary>
        /// Save settings
        /// </summary>
        public abstract void SaveSettings();
        /// <summary>
        /// Run Setting Dialog for view model <see cref="IViewModelTop"/>
        /// </summary>
        /// <typeparam name="T"><see cref="IViewModelTop"/></typeparam>
        /// <param name="view"><see cref="IViewModelTop"/></param>
        /// <returns><see cref="WindowResult"/></returns>
        public virtual WindowResult ShowSettings<T>(T view) where T : IViewModelTop
        {
            view.ShowWarning(CategorizedBy.SettingsManager.NoSettingsDialogMessage);

            return WindowResult.Exception;
        }
        /// <summary>
        /// Get all invalid properties
        /// </summary>
        public override IReadOnlyList<string> InvalidProperties
        {
            get
            {
                return
                    new ReadOnlyCollection<string>((from pair in Dictionary where !pair.Value select pair.Key).ToList());
            }
        }
        /// <summary>
        /// Get all valid properties
        /// </summary>
        public override IReadOnlyList<string> ValidProperties
        {
            get
            {
                return
                    new ReadOnlyCollection<string>((from pair in Dictionary where pair.Value select pair.Key).ToList());
            }
        }
    }
}